" Some tools for Vim
" Last Change: 2021-10-19
" Author: Kong Jun <kongjun18@outlook.com>
" Github: https://github.com/kongjun18
" License: GPL-2.0

" guard
if exists('g:loaded_tools_vim') || &cp || version < 700
    finish
endif
let g:loaded_tools_vim = 1

" create_gitignore() -- Create gitignore template
" @brief: Create .gitignore file for C/C++
" @note:     only impletemt c and cpp
function tools#create_gitignore(filetype)
    if filereadable('.gitignore')
        echomsg "This project had .gitignore"
        return
    endif

    if a:filetype == 'c' ||  a:filetype == 'cpp'
        try
            call utils#copy(g:general#vimfiles .. '/' .. 'tools' .. '/' .. 'gitignore' .. '/' .. 'c_gitignore', utils#get_root_dir(utils#current_path()))
        catch *
            echomsg "Error in tools#create_gitignore()"
        endtry
    else
        echomsg "This type don't impletemted"
    endif
endfunction

function tools#create_vimspector(path)
    call utils#copy(g:general#vimfiles .. '/' .. 'tools' .. '/' .. '.vimspector.json', a:path)
endfunction

" Delete gtags generated by gutentags of a:project_dir
" depends asyncrun and gutentags
" If gtags encounters errors, call this function to delete gtags generated by
" gutentags and run :GutentagsUpdate
"
" For example, :call tools#rm_tags(asyncun#get_root('%'))
function g:tools#rm_gtags(project_dir)
    let l:gtags_dir = a:project_dir
    if l:gtags_dir[0] != '~' && l:gtags_dir[0] != '/'
        echoerr "tools#rm_tags: argument error"
    endif
    if l:gtags_dir[0] == '~'
        let l:gtags_dir = substitute(l:gtags_dir, '~', "$HOME")
    endif
    let l:gtags_dir = substitute(l:gtags_dir, '\/', '-', 'g')
    let l:gtags_dir = substitute(l:gtags_dir, '^-', '\/', '')
    let l:gtags_dir = trim(l:gtags_dir)
    let l:gtags_dir = printf("%s%s", g:gutentags_cache_dir, l:gtags_dir)
    if delete(l:gtags_dir, 'rf') != 0
        echoerr "Can't delete tag directory " . l:gtags_dir
    endif
endfunction
"

" scroll_adjacent_window() -- Scroll adjcent window without change focus
" @para dir  0 -- up  1 -- down
" @para mode 'i' -- insert 'n' -- normal
" @complain neovim don't support win_execute(). What a pity!!!
function tools#scroll_adjacent_window(dir, mode)
    function! s:switch_to_insert(old_cursor, tail_cursor) abort
        if a:old_cursor == a:tail_cursor - 1
            noautocmd wincmd p
            startinsert!
        else
            noautocmd wincmd p
            exec 'normal l'
            noautocmd wincmd p
            startinsert
            noautocmd wincmd p
        endif
    endfunction

    let left_winnr = winnr('h')
    let right_winnr = winnr('l')
    let cur_winnr = winnr()
    let old_cursor = col(".")
    let tail_cursor = col("$")
    if left_winnr <= 0 && right_winnr <= 0
        echomsg "Unknown error in tools#scroll_adjacent_window()"
        if a:mode == 'i'
            call s:switch_to_insert(old_cursor, tail_cursor)
        endif
        return
    endif

    " only one window?
    if left_winnr == right_winnr
        echomsg "Only a single window"
        if a:mode == 'i'
            call s:switch_to_insert(old_cursor, tail_cursor)
        endif
        return
    endif

    let win_num = tabpagewinnr(tabpagenr(), '$')
    if  win_num != 2 && !(win_num == 3 && getqflist({'winid': 0}).winid != 0)
        echomsg "More than two adjcent windows"
        if a:mode == 'i'
            call s:switch_to_insert(old_cursor, tail_cursor)
        endif
        return
    endif

    let go_direction = 'h'
    let back_direction = 'l'
    if right_winnr != cur_winnr
        let go_direction = 'l'
        let back_direction = 'h'
    endif

    noautocmd silent! wincmd p
    if a:dir == 0
        exec "normal! \<ESC>\<C-W>" . go_direction . "\<C-U>\<C_W>" . back_direction
    elseif a:dir == 1
        exec "normal! \<ESC>\<C-W>" . go_direction . "\<C-D>\<C_W>" . back_direction
    endif
    " scroll in insert mode?
    if a:mode == 'i'
        call s:switch_to_insert(old_cursor, tail_cursor)
    else
        noautocmd wincmd p
    endif
endfunction
"

" scroll_quickfix() -- Scroll quickfix without change focus
" @param dir  0 -- up  1 -- down
" @param mode 'i' -- insert 'n' -- normal
"
function tools#scroll_quickfix(dir, mode)
    let current_winid = win_getid()
    let quickfix_winid = getqflist({'winid': 0}).winid
    if quickfix_winid == 0
        echomsg "There is no quickfix window"
        return
    endif
    " scroll
    call win_gotoid(quickfix_winid)
    if a:dir == 0
        exec "normal! \<C-U>"
    elseif a:dir == 1
        exec "normal! \<C-D>"
    endif
    call win_gotoid(current_winid)
    if a:mode == 'i'
        exec "normal! l"
        startinsert
    endif

endfunction

" create_qt_project() -- Create Qt project
" TODO: refactor code to platform-dependent
function tools#create_qt_project(type, to)
    if a:type != "QMainWindow" && a:type != "QWidget" && a:type != "QDialog"
        echoerr "Please input correct argument"
    endif
    if !isdirectory(a:to)
        echoerr "Please input correct argument"
    endif
    call system("cp " . "$HOME/.config/nvim/tools/Qt/" . a:type . "/* " . a:to)
    call writefile("", ".root")
    silent !cmake -S. -B_builds
    call system("ln -s _builds/compile_commands.json .")
    if !v:shell_error
        echomsg "create_qt_project(): successfull"
    else
        echomsg "create_qt_project(): failed to copy templates"
    endif
endfunction
"

" Integrate lightline and ale
function! g:LightlineLinterWarnings() abort
    let l:counts = ale#statusline#Count(bufnr(''))
    let l:all_errors = l:counts.error + l:counts.style_error
    let l:all_non_errors = l:counts.total - l:all_errors
    return l:counts.total == 0 ? '' : printf('%d ▲', all_non_errors)
endfunction

function! g:LightlineLinterErrors() abort
    let l:counts = ale#statusline#Count(bufnr(''))
    let l:all_errors = l:counts.error + l:counts.style_error
    let l:all_non_errors = l:counts.total - l:all_errors
    return l:counts.total == 0 ? '' : printf('%d ✗', all_errors)
endfunction

function! g:LightlineLinterOK() abort
    let l:counts = ale#statusline#Count(bufnr(''))
    let l:all_errors = l:counts.error + l:counts.style_error
    let l:all_non_errors = l:counts.total - l:all_errors
    return l:counts.total == 0 ? '✓' : ''
endfunction
"

" Debug gutentags
function tools#debug_gutentgs()
    let g:gutentags_define_advanced_commands = 1
    let g:gutentags_trace = 1
endfunction

function tools#undebug_gutentags()
    let g:gutentags_define_advanced_commands = 0
    let g:gutentags_trace = 0
endfunction
"

" Switch tag system
"
" Vim-gutentags only generate tags which reside in g:gutentags_modules.
" Tag generation needs the tag module resides in the job management data
" structures (s:update_queue and s:update_in_progress) and tag file path
" is keeped in b:gutentags_files. Thus, I just need to add those into
" vim-gutentags' variables when switch to static tag system and do nothing
" when switch to LSP tag system. Unfortunately, vim-gutentags don't expose
" gutentags#add_in_progress() which I need, I have to fork vim-gutentags.
function tools#use_static_tag() abort
    let g:general#only_use_static_tag = 1
    if executable('gtags-cscope') && executable('gtags')
        set csprg=gtags-cscope
        let gtags_in_modules = v:false
        for module in g:gutentags_modules
            if module == 'gtags-cscope'
                let gtags_in_modules = v:true
            endif
        endfor
        if !gtags_in_modules
            let g:gutentags_modules += ['gtags_cscope']
            for buf in getbufinfo({'buflisted':1})
                let l:gutentags_files = getbufvar(buf.bufnr, 'gutentags_files')
                " If the buffer enables vim-gutentags
                if type(l:gutentags_files) != v:t_string "
                    " Add module gtags_cscope into job management data structures
                    " (s:update_queue and s:update_in_progress) and initialize
                    " module(set up b:gutentags_files and so on).
                    call gutentags#add_in_progress('gtags_cscope')
                    call gutentags#gtags_cscope#init(getbufvar(buf.bufnr, 'gutentags_root'))
                endif
            endfor
        endif
    endif
    if &csprg == 'gtags-cscope'
        nnoremap <silent> gs :GscopeFind s <C-R><C-W><cr>:cnext<CR>zz
        nnoremap <silent> gd :GscopeFind g <C-R><C-W><cr>:cnext<CR>zz
        nnoremap <silent> gc :GscopeFind c <C-R><C-W><cr>:cnext<CR>zz
        nnoremap <silent> gt :GscopeFind t <C-R><C-W><cr>:cnext<CR>zz
        nnoremap <silent> gC :GscopeFind d <C-R><C-W><cr>:cnext<CR>zz
        nnoremap <silent> gi :GscopeFind i <C-R>=expand("<cfile>")<cr><cr>:cnext<CR>zz
        nnoremap <silent> ga :GscopeFind a <C-R><C-W><cr>:cnext<CR>zz
    else
        nnoremap <silent> gc :echoerr 'gtags-scope is not available'<CR>
        nnoremap <silent> gt :echoerr 'gtags-scope is not available'<CR>
        nnoremap <silent> gs :echoerr 'gtags-scope is not available'<CR>
        nnoremap <silent> gd :echoerr 'gtags-scope is not available'<CR>
        nnoremap <silent> gC :echoerr 'gtags-scope is not available'<CR>
        nnoremap <silent> gi :echoerr 'gtags-scope is not available'<CR>
    endif
    if &filetype == 'rust'
        nnoremap <silent> gi :echoerr 'Rust does not use header/source model'<CR>
    endif

    " Unmap coc.nvim local mapping gc/gC in C/C++ buffers
    let old_lazyredraw = &lazyredraw
    let curr_buf = bufname()
    try
        for buf in getbufinfo({'buflisted':1})
            let l:filetype = getbufvar(buf.bufnr, '&filetype')
            if l:filetype == 'c' || l:filetype == 'cpp'
                execute 'buffer ' .. bufname(buf.bufnr)
                if maparg('gc', 'n')
                    nunmap <buffer> gc
                endif
                if maparg('gC', 'n')
                    nunmap <buffer> gC
                endif
            endif
        endfor
    finall
        let &lazyredraw = old_lazyredraw
        execute 'buffer ' .. curr_buf
    endtry
endfunction

function tools#use_lsp_tag() abort
    let g:general#only_use_static_tag = 0
    nmap <silent> gd <Plug>(coc-definition)
    nmap <silent> gs <Plug>(coc-references)
    nmap <silent> gt <Plug>(coc-type-definition)
    nmap <silent> gi <Plug>(coc-implementation)
    if maparg('gc', 'n') =~? 'GscopeFind'
        :nunmap gc
    endif
    if maparg('gC', 'n') =~? 'GscopeFind'
        :nunmap gC
    endif

    let index = 0
    for module in g:gutentags_modules
        if module == 'gtags_cscope'
            call remove(g:gutentags_modules, index)
        endif
        let index += 1
    endfor
    let &csprg = 'cscope'

    " Remap coc.nvim maping gc/gC in C/C++ buffers
    let old_lazyredraw = &lazyredraw
    let curr_buf = bufname()
    try
        for buf in getbufinfo({'buflisted':1})
            let l:filetype = getbufvar(buf.bufnr, '&filetype')
            if l:filetype == 'c' || l:filetype == 'cpp'
                execute 'buffer ' .. bufname(buf.bufnr)
                nmap <buffer> <silent> gc :call CocLocations('ccls','$ccls/call')<CR>
                nmap <buffer> <silent> gC :call CocLocations('ccls','$ccls/call', {'callee': v:true})<CR>
            endif
        endfor
    finall
        let &lazyredraw = old_lazyredraw
        execute 'buffer ' .. curr_buf
    endtry
endfunction
